#!/usr/bin/python
# coding: utf-8

import sys
import os
import json
import time
import datetime
import MySQLdb
from optparse import OptionParser
import commands
import shutil

os.environ["PATH"] += ":/usr/local/xtrabackup/bin:/opt/soft/xtrabackup/bin"
os.environ["PATH"] += ":/usr/local/bin/"


class xtrabackup(object):
    def __init__(self, user, passwd, host, port, mycnf, backupDir,storeDir):
        self.user = user;
        self.passwd = passwd
        self.host = host;
        self.port = port;
        self.baseDir = os.path.join(backupDir, self.port)
        self.storeDir = storeDir
        self.subDir = self.port + "_" + time.strftime("%Y-%m-%d_%H", time.localtime())
        self.mycnf = mycnf;
        self.backDir = os.path.join(self.baseDir, self.subDir)
        self.parallel = 4;
        self.throttle = 1000;
        self.kill_query_timeout = 200
        self.xtraLsnDir = os.path.join(self.baseDir, "xtralsn/" + self.subDir)
        self.metaFile = os.path.join(self.baseDir, ".meta")
        self.xtraBaseCmd = \
            "innobackupex --defaults-file={self.mycnf} --user={self.user} --password={self.passwd} --host={self.host} \
            --kill-long-queries-timeout={self.kill_query_timeout} \
            --port={self.port}  --backup-locks  --slave-info --ibbackup=/usr/local/xtrabackup/bin/xtrabackup \
            --tmpdir={self.baseDir} --safe-slave-backup --safe-slave-backup-timeout=60 --parallel={self.parallel} \
            --throttle={self.throttle} --extra-lsndir={self.xtraLsnDir}  --no-timestamp ".format(self=self)
        self.backLog = os.path.join(self.xtraLsnDir, self.port + "_backlog")

    def fullBackup(self, compress=False, dropOrigin=True):
        resp = {"BackupType": "FULL", "BackupCmd": "Xtrabackup", "Compress": compress}
        fullBackupCmd = self.xtraBaseCmd + self.backDir + " > %s 2>&1" % self.backLog
        if self.__backupAction(fullBackupCmd, resp) and compress:
            if self.__tarCompress(resp) and dropOrigin:
                self.__dropOriginBackupDir(resp)
        self.__storeToDir(self.backDir,resp)
        self.__respToMetaFile("w+", resp)
        return resp

    def incrementalBackup(self, compress=False, dropOrigin=True):
        resp = {"BackupType": "INCREMENTAL", "BackupCmd": "Xtrabackup", "Compress": compress}
        lastBackDir = self.__getLastBackDir()
        if not lastBackDir:
            resp["BackupStatus"] = 1
            return resp
        resp["LastBackDir"] = lastBackDir
        incrementalBackupCmd = self.xtraBaseCmd + self.backDir + " --incremental --incremental-basedir=%s  > %s 2>&1" % (
        lastBackDir, self.backLog)
        if self.__backupAction(incrementalBackupCmd, resp) and compress:
            if self.__tarCompress(resp) and dropOrigin:
                self.__dropOriginBackupDir(resp)
        self.__storeToDir(self.backDir,resp)
        self.__respToMetaFile("a+", resp)
        return resp

    def streamFullBackup(self, compress=True):
        resp = {"BackupType": "FULL", "BackupCmd": "Xtrabackup&Xstream", "Compress": compress}
        completeBackFile = os.path.join(self.baseDir, self.port + "_full_stream_" + self.subDir + ".bz2")
        streamFullBackupCmd = self.xtraBaseCmd + self.baseDir + " --stream=xbstream 2>> %s" % self.backLog
        if compress: streamFullBackupCmd += " | lbzip2 -n 8 "
        streamFullBackupCmd += " > %s" % completeBackFile
        self.__backupAction(streamFullBackupCmd, resp)
        
        resp["BackupSize"] = "null"
        if resp["BackupStatus"] == 0:
                self.__storeToDir(completeBackFile,resp)
        self.__respToMetaFile("w+", resp)
        return resp

    def streamIncrementalBackup(self, compress=True):
        resp = {"BackupType": "INCREMENTAL", "BackupCmd": "Xtrabackup&Xstream", "Compress": compress}
        lastBackDir = self.__getLastBackDir()
        if not lastBackDir:
            resp["BackupStatus"] = 1
            return resp
        resp["LastBackDir"] = lastBackDir
        completeBackFile = os.path.join(self.baseDir, self.port + "_incremental_stream_" + self.subDir + ".bz2")
        streamIncrementalBackupCmd = self.xtraBaseCmd + self.baseDir + " --stream=xbstream --incremental --incremental-basedir=%s 2>> %s" % (
        lastBackDir, self.backLog)
        if compress: streamIncrementalBackupCmd += " | lbzip2 -n 8 "
        streamIncrementalBackupCmd += " > %s " % completeBackFile
        self.__backupAction(streamIncrementalBackupCmd, resp)
        
        resp["BackupSize"] = "null"
        if resp["BackupStatus"] == 0:
                self.__storeToDir(completeBackFile,resp)

        self.__respToMetaFile("a+", resp)

        return resp

    def __tarCompress(self, resp):
        resp["CompressStatus"] = 0
        flag = "_incremental_" if "BackupType" in resp and resp["BackupType"] == "INCREMENTAL" else "_full_"
        completeBackFile = os.path.join(self.baseDir, self.port + flag + self.subDir + ".tar.bz2")
        resp["CompressStartTimestamp"] = time.time()
        try:
            os.chdir(self.baseDir)
            compressCmd = "tar -cvf - {tarDir}  | lbzip2 -n 8 > {backFile} ".format(tarDir=self.subDir,
                                                                                    backFile=completeBackFile)
            resp["CompressStatus"] = os.system(compressCmd)
        except Exception, e:
            resp["CompressException"] = str(e)
        resp["CompressEndTimestamp"] = time.time()
        return not resp["CompressStatus"]

    def __backupAction(self, backupCmd, resp):
        resp["BackupStartTimestamp"] = time.time()
        resp["BackupStatus"] = 0
        resp["xtraLsnDirPath"] = self.xtraLsnDir
        try:
            os.system("/bin/mkdir -p %s" % self.xtraLsnDir)
            resp["BackupStatus"] = os.system(backupCmd)
        except Exception, e:
            resp["BackupException"] = str(e)
        resp["BackupEndTimestamp"] = time.time()
        if not self.__checkBackLog():
            resp["BackupStatus"] = 255

        return not int(resp["BackupStatus"])

    def __respToMetaFile(self, mode, resp):
        with open(self.metaFile, mode) as mf:
            mf.write(json.dumps(resp) + "\n")

    def __storeToDir(self, srcDir,resp):
        fileName = self.port + "_" + resp['BackupType'].lower() + "_stream_" + self.subDir + ".bz2"
        strcmd = "ls -lh %s| grep %s | awk '{print $5;}'" % (self.baseDir, fileName)
        (status, output) = commands.getstatusoutput(strcmd)
        if output == "":
            resp["BackupSize"] = "null"
        else:
            resp["BackupSize"] = output

        if (not self.storeDir == "nodir") and resp["BackupType"] == "FULL":
            str_cmd = "ls %s | grep %s" % (self.baseDir, self.port)
            (status,output) = commands.getstatusoutput(str_cmd)
            output = output.split('\n')
            print output
            for str_file_name in output:
                if not str_file_name == fileName:
                    srcDir = os.path.join(self.baseDir,str_file_name)
                    print srcDir
                    sql = "rsync -auvzP --bwlimit=30720 %s %s" % (srcDir, self.storeDir)
                    (status, output) = commands.getstatusoutput(sql)
                    if status == 0:
                        os.system("rm -f  %s" % srcDir)


    def __move_store_dir(self, srcDir):
        sql = "rsync -auvzP --bwlimit=30720 %s %s" % (srcDir, self.storeDir)
        (status, output) = commands.getstatusoutput(sql)
        if status == 0:
            os.system("rm -f  %s" % srcDir)

    def __getLastBackDir(self):
        metaDict = {}
        if not os.path.isfile(self.metaFile): return None
        with open(self.metaFile, "r") as mf:
            try:
                metaDict = json.loads(mf.readlines()[-1])
            except:
                return None
        if "BackupStatus" in metaDict and not int(metaDict["BackupStatus"]):
            if "xtraLsnDirPath" in metaDict and os.path.isdir(metaDict["xtraLsnDirPath"]):
                if os.path.isfile(os.path.join(metaDict["xtraLsnDirPath"], "xtrabackup_checkpoints")):
                    return metaDict["xtraLsnDirPath"]
        return None

    def __checkBackLog(self):
        with open(self.backLog, "r") as log:
            str_last_line = ''
            for line in log:
                if line:
                    str_last_line = line

            if str_last_line.find("completed OK!") != -1:
                return True

        return False

    def __dropOriginBackupDir(self, resp):
        if self.backDir == "/" or not os.path.isdir(self.backDir):
            resp["DropOriginException"] = "backup dir not found"
            return
        try:
            os.system("rm -rf %s" % self.backDir)
        except Exception, e:
            resp["DropOriginException"] = str(e)

    def addbackuptodb(self, resp, dbtime,insert):
        dbhost = "10.8.104.200"
        dbport = 3308
        dbuser = 'user_dbmp'
        passwd='77eHI47vp38p3LhohFHg'
        dbName = "dbmp_db"
        db_port = self.port
        dbtime = seconds_to_str(dbtime)

        db = MySQLdb.connect(dbhost,dbuser,passwd,dbName,dbport,charset="utf8")
        cursor = db.cursor()
        sql = 'select name from backup_project where id = %s ;' % (insert)
        try:
            cursor.execute(sql)
            result = cursor.fetchall()
            project_name = result[0][0]
        except:
            project_name = '未知项目'

        fileName = self.port + "_" + resp['BackupType'].lower() + "_stream_" + self.subDir + ".bz2"

        sql = 'insert into backup_info values ("%s","%s","%s","%s","%s","%s","%s","%s",null,"%s",-1)' % \
              (fileName, resp['BackupType'], datetime.datetime.now().strftime("%Y-%m-%d"), resp["BackupSize"], resp['Compress'], dbtime, resp['BackupStatus'], db_port,project_name)
        try:
            cursor.execute(sql)
            db.commit()
        except:
            db.rollback()
        db.close()

def seconds_to_str(seconds):
    tmlist = [0, 0, 0, 0, 0, 0, 0, 0, 0]
    tmlist[3] = seconds/3600
    tmlist[4] = (seconds - tmlist[3]*3600)/60
    tmlist[5] = (seconds - tmlist[3]*3600 - tmlist[4]*60)
    return time.strftime("%H:%M:%S", tmlist)


def main(argv):
    parser = OptionParser(version="%prog 1.0.0")
    parser.add_option("-f", "--full-backup", dest="fullday", help="day of week for full backup",
                      metavar="1,2,3,4,5,6,7", default="1,2,3,4,5,6,7")
    parser.add_option("-H", "--host", dest="host", help="back up for hostname",
                      metavar="x.x.x.x")
    parser.add_option("-P", "--port", dest="port", help="back up for host port",
                      metavar="3306", default="3306")
    parser.add_option("-c", "--conf", dest="conf", help="back up by conf",
                      metavar="/opt/soft/mysql/my_$port.cnf")
    parser.add_option("-d", "--back-dir", dest="backdir", help="back up to dir",
                      metavar="/data/backup", default="/data/backup")
    parser.add_option("-i", "--insert-table", dest="insert", help="insert into table of backup info",
                      metavar="False", default=False)
    parser.add_option("-s","--store-dir",dest="storedir",help="Store backup directory",
                      metavar="nodir", default="nodir")

    dayOfWeek = datetime.datetime.today().weekday() + 1
    (options, args) = parser.parse_args()
    if not len(argv): parser.print_help();sys.exit(1)
    if not options.host or not options.port:
        print "backup: host and port must be set!"
        sys.exit(1)
    if not options.conf:
        options.conf = "/opt/soft/mysql/my_" + options.port + ".cnf"
    _backup = xtrabackup("user_backup", "PLwh6isJpZqg3kDrALpM", options.host, options.port, options.conf,
                         options.backdir,options.storedir)
    resp = []

    a = datetime.datetime.now()
    if str(dayOfWeek) in options.fullday.split(","):
        resp = _backup.streamFullBackup(compress=True)
    else:
        resp = _backup.streamIncrementalBackup(compress=True)
        if int(resp["BackupStatus"]):
            resp = _backup.streamFullBackup(compress=True)
    b = datetime.datetime.now()

    if options.insert:
        _backup.addbackuptodb(resp, (b-a).seconds, options.insert)

    return resp

if __name__ == "__main__":
    print main(sys.argv[1:])

